﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Babel;
using System.Management.Automation.Runspaces;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Xml;
using System.Threading;
using System.Globalization;
using System.IO;

namespace Evaluant.PS4VS.Language
{
    class Resolver : IASTResolver
    {
        static object runSpaceLock = new object();
        static Runspace myRunSpace;
        static Dictionary<string, XmlDocument> helpFiles = new Dictionary<string, XmlDocument>();
        static Renderer.VSHost vsHost;
        static Resolver()
        {
            RunspaceConfiguration config = RunspaceConfiguration.Create();
            myRunSpace = RunspaceFactory.CreateRunspace(config);
            myRunSpace.Open();
        }

        static PSHostUserInterface ui;

        public static PSHostUserInterface UI
        {
            get { return ui; }
            set
            {
                lock (runSpaceLock)
                {
                    myRunSpace.Close();
                    ui = value;
                    RunspaceConfiguration config = RunspaceConfiguration.Create();
                    vsHost = new Evaluant.PS4VS.Renderer.VSHost();
                    vsHost.SetUI(ui);
                    myRunSpace = RunspaceFactory.CreateRunspace(vsHost, config);
                    myRunSpace.Open();
                }
            }
        }

        #region IASTResolver Members

        public static IEnumerable<CmdletConfigurationEntry> GetCommands()
        {
            return myRunSpace.RunspaceConfiguration.Cmdlets.OrderBy(c => c.Name);
            //try
            //{
            //    return Execute("Get-Command").Select(p => p.BaseObject).OfType<CommandInfo>();
            //}
            //catch (Exception e)
            //{
            //    Console.WriteLine(e);
            //    return Execute("Get-Command").Select(p => p.BaseObject).OfType<CommandInfo>();
            //}
        }

        public static System.Collections.IEnumerable GetAliases()
        {
            return myRunSpace.RunspaceConfiguration.Cmdlets;
        }

        private static ICollection<PSObject> Execute(string line)
        {
            try
            {
                lock (runSpaceLock)
                {
                    Pipeline pipe = myRunSpace.CreatePipeline(line);
                    pipe.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
                    pipe.Commands.Add("out-default");
                    return pipe.Invoke();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return new List<PSObject>();
            }

        }

        public IList<Declaration> FindCompletions(object result, int line, int col)
        {
            IList<Declaration> declarations = new List<Declaration>();
            foreach (CmdletConfigurationEntry info in GetCommands())
            {
                XmlDocument doc = GetHelpFile(info);
                if (doc != null)
                {
                    XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
                    nsManager.AddNamespace("command", "http://schemas.microsoft.com/maml/dev/command/2004/10");
                    nsManager.AddNamespace("maml", "http://schemas.microsoft.com/maml/2004/10");
                    nsManager.AddNamespace("dev", "http://schemas.microsoft.com/maml/dev/2004/10");
                    nsManager.AddNamespace("msh", "http://msh");
                    XmlNode description = doc.SelectSingleNode("/*[local-name()='helpItems']/command:command[command:syntax/command:syntaxItem/maml:name/text()='" + info.Name + "']/command:details/maml:description/maml:para/text()", nsManager);
                    declarations.Add(new Declaration(description == null ? info.Name : description.Value, info.Name, 0, info.Name));
                }
                else
                    declarations.Add(new Declaration(info.Name, info.Name, 0, info.Name));
            }
            return declarations;
        }

        public IList<Declaration> FindMembers(object result, int line, int col)
        {
            return new List<Declaration>();
        }

        public string FindQuickInfo(object result, int line, int col)
        {
            return "";
        }

        private XmlDocument GetHelpFile(CmdletConfigurationEntry entry)
        {
            XmlDocument doc = null;
            if (!string.IsNullOrEmpty(entry.HelpFileName))
            {
                if (helpFiles.ContainsKey(entry.HelpFileName))
                    doc = helpFiles[entry.HelpFileName];
                else
                {
                    string path;
                    if (entry.PSSnapIn.ApplicationBase.EndsWith("\\"))
                        path = entry.PSSnapIn.ApplicationBase + entry.HelpFileName;
                    else
                        path = entry.PSSnapIn.ApplicationBase + "\\" + entry.HelpFileName;
                    if (!File.Exists(path))
                    {
                        CultureInfo culture = Execute("Get-Culture").FirstOrDefault().BaseObject as CultureInfo;
                        if (entry.PSSnapIn.ApplicationBase.EndsWith("\\"))
                            path = entry.PSSnapIn.ApplicationBase + culture.Name + "\\" + entry.HelpFileName;
                        else
                            path = entry.PSSnapIn.ApplicationBase + "\\" + culture.Name + "\\" + entry.HelpFileName;
                    }
                    if (!File.Exists(path))
                    {
                        if (entry.PSSnapIn.ApplicationBase.EndsWith("\\"))
                            path = entry.PSSnapIn.ApplicationBase + "en-US\\" + entry.HelpFileName;
                        else
                            path = entry.PSSnapIn.ApplicationBase + "\\en-US\\" + entry.HelpFileName;
                    }
                    if (!File.Exists(path))
                        helpFiles.Add(entry.HelpFileName, null);
                    else
                    {
                        doc = new XmlDocument();
                        doc.Load(path);
                        helpFiles.Add(entry.HelpFileName, doc);
                    }
                }
            }
            return doc;
        }

        public IList<Method> FindMethods(object result, int line, int col, string name)
        {
            IList<Method> methods = new List<Method>();
            CmdletConfigurationEntry entry = GetCommands().Single(c => c.Name == name);
            XmlDocument doc = GetHelpFile(entry);
            if (doc != null)
            {
                XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
                nsManager.AddNamespace("command", "http://schemas.microsoft.com/maml/dev/command/2004/10");
                nsManager.AddNamespace("maml", "http://schemas.microsoft.com/maml/2004/10");
                nsManager.AddNamespace("dev", "http://schemas.microsoft.com/maml/dev/2004/10");
                nsManager.AddNamespace("msh", "http://msh");
                foreach (XmlNode node in doc.SelectNodes("/*[local-name()='helpItems']/command:command/command:syntax/command:syntaxItem[maml:name='" + name + "']", nsManager))
                {
                    Reflection.Cmdlet cmdlet = new Reflection.Cmdlet(name);
                    foreach (XmlNode param in node.SelectNodes("command:parameter", nsManager))
                    {
                        cmdlet.Parameters.Add(Reflection.Parameter.CreateParameter(
                            param.SelectSingleNode("maml:name/text()", nsManager).Value,
                            param.Attributes["position"].Value,
                            param.Attributes["required"].Value,
                            param.SelectSingleNode("maml:description/maml:para/text()", nsManager).Value
                        ));
                    }
                    foreach (Method m in cmdlet.GetMethods())
                        methods.Add(m);
                }
            }
            //Execute("Get-Location -PSProvider FileSystem");
            return methods;
        }

        #endregion

        public static ICollection<PSObject> RunScript(string scriptPath)
        {
            return Execute(scriptPath);
        }
    }
}
